package com.smile.frame.websocket.netty.service;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.net.InetSocketAddress;

/**
 * websocket程序
 *
 * @author LGC
 */
@Slf4j
@Component
public class WebsocketServer {

    @Resource
    private WebsocketChannelInitializer websocketChannelInitializer;

    @Value("${websocket.ip}")
    private String ip;

    @Value("${websocket.port}")
    private Integer port;


    public void start() {
        //bossGroup连接线程组，主要负责接受客户端连接，一般一个线程足矣
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //workerGroup工作线程组，主要负责网络IO读写
        EventLoopGroup workerGroup = new NioEventLoopGroup(2);
        try {
            //启动辅助类
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //bootstrap绑定两个线程组
            serverBootstrap.group(bossGroup, workerGroup);
            //设置通道为NioChannel
            serverBootstrap.channel(NioServerSocketChannel.class);
            //服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
            serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
            //将小的数据包包装成更大的帧进行传送，提高网络的负载,即TCP延迟传输 设置为不延时
            serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
            //可以对入站\出站事件进行日志记录，从而方便我们进行问题排查。
            serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
            //设置自定义的通道初始化器，用于入站操作
            serverBootstrap.childHandler(websocketChannelInitializer);

            ChannelFuture channelFuture = null;
            //启动成功标识
            boolean startFlag = false;
            //启动失败时，多次启动，直到启动成功为止
            while (!startFlag) {
                try {
                    //启动服务器,本质是Java程序发起系统调用，然后内核底层起了一个处于监听状态的服务，生成一个文件描述符FD
                    channelFuture = serverBootstrap.bind(port).sync();
                    startFlag = true;
                } catch (Exception e) {
                    log.info("端口号：" + port + "已被占用！");
                    port++;
                    log.info("尝试一个新的端口：" + port);
                    //重新便规定端口号
                    serverBootstrap.localAddress(new InetSocketAddress(port));
                }
            }

            //服务端启动监听事件
            channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
                @Override
                public void operationComplete(Future<? super Void> future) throws Exception {
                    //启动成功后的处理
                    if (future.isSuccess()) {
                        log.info("websocket服务器启动成功，Started Success:" + ip + ":" + port);
                    } else {
                        log.info("websocket服务器启动失败，Started Failed:" + ip + ":" + port);
                    }
                }
            });

            try {
                // 监听通道关闭事件
                // 应用程序会一直等待，直到channel关闭
                ChannelFuture closeFuture = channelFuture.channel().closeFuture();
                closeFuture.sync();
            } catch (Exception e) {
                e.printStackTrace();
                log.error("发生其他异常", e);
            } finally {
                // 优雅关闭EventLoopGroup
                // 释放掉所有资源包括创建的线程
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }

        } catch (Exception e) {
            log.error("websocket发生错误：", e);
        }

    }

}